using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using PriorityQueue;

namespace MUSA
{
    #region Global ID 

    /*
     * note: Tank/State Interface change
     *  Tank will be hollowed out and reduced the basic movement control interface of
     *  forward, reverse, turn left, turn right and fire. States will be given the task
     *  of interpreting actions for the tank in addition to its task of decideing actions.
     * 
     *  The tank will be given a function pointer for collision events, the function will
     *  be supplied by the current state in the tank's state machine.
     * 
     * The reason for the change in the Tank/State relationship is it percieved to be easier
     * for reactionary actions.
     * 
     * Notes on the previous method. We divided the AI into two components. Tanks were responsible 
     * for executing the commands of the state, while the states were solely repsonsible for
     * determining the commands.
     */

    //static class ID
    //{
    //    private static Dictionary<TeamColor , ushort> idList = new Dictionary<TeamColor,ushort>();

    //    static public ushort CreateID(TeamColor color)
    //    {
    //        if ( idList.ContainsKey(color) )
    //        {
    //            return ++(idList[color]);
    //        }
    //        else
    //        {
    //            idList.Add(color , 0);
    //            return 0;
    //        }
    //    }
    //}

    static class ID
    {
        private static ushort mID_List = 0;

        static public ushort CreateID()
        {
            return ++mID_List;
        }
    }

    #endregion Global ID

    #region Enum

    enum TeamColor
    {
        RED ,
        BLUE ,
        GREEN ,
        YELLOW , 
        NONE
    }

    #endregion Enum

    #region Messages

    //Why is this here again?
    class Messages
    {
        //int priority;

        //Enums for actions
        //or Delegates
    }

    #endregion

    class Tank : Object, Collision
    {
        #region Member Variables

        Game gameRef;                       //reference to running game(simulation)

        protected int healthPoint;          //tank unit's health
        protected bool dead;                //tank's life status

        protected Vector3 direction;        //velocity

        protected float turnSpeed;          //degrees per second
        protected float moveSpeed;          //movement speed per second

        protected float fireTime;           //time passed since fire last shot
        protected float fireTimeLimit;      //time required to pass since last fired
        protected float range;              //attack range

        protected CollisionInfo mTargetObject = null;

        public StateMachine mStateMachine;

        protected TeamColor team;
        protected ushort tankID;

        protected TankState mCurrentState;

        protected PriorityQueue<Messages> processMessages = new PriorityQueue<Messages>();

        //bounding volumes
        protected BoundingSphere tankBoundingSphere;
        protected BoundingSphere sensorBoundingSphere;

        //Group Leader Position
        Vector3 mGLPos = new Vector3(0);

        #endregion     

        #region Properties

        public TankState CurrentState
        {
            get
            {
                return mCurrentState;
            }
            set
            {
                mCurrentState = value;
            }
        }

        public float MoveSpeed
        {
            get
            {
                return moveSpeed;
            }
        }

        public Vector3 Direction
        {
            get
            {
                return direction;
            }

            set
            {
                direction = value;
            }
        }

        public TeamColor Team
        {
            get
            {
                return team;
            }
        }

        public ushort TankID
        {
            get
            {
                return tankID;
            }
        }

        public int HP
        {
            get
            {
                return healthPoint;
            }
        }

        public CollisionInfo TargetObject
        {
            get
            {
                return mTargetObject;
            }
            set
            {
                mTargetObject = value;
            }
        }

        public Messages ProcessedMessage
        {
            get
            {
                if (processMessages.Count != 0)
                    return processMessages.Dequeue();

                return null;
            }
        }

        public bool Dead
        {
            get
            {
                return dead;
            }
        }

        #endregion Properties

        #region Constructor

        public Tank(Game game)
            : base(game)
        {
            mStateMachine = new StateMachine(this);
            mType = ObjectType.DYNAMIC;
        }

       
        public Tank(Game game , Vector3 nPosition , Vector3 nDirection, TeamColor nTeam)
            : base(game)
        {
            gameRef = game;

            mStateMachine = new StateMachine(this);

            mType = ObjectType.DYNAMIC;
            team = nTeam;           //set your current team
            
            healthPoint = 50;
            dead = false;

            position = nPosition;

            direction = nDirection;
            direction.Normalize();

            turnSpeed = (float)(60 * Math.PI) / 180;
            turnSpeed *= 2;
            moveSpeed = 10.0f;

            fireTime = 0.0f;
            fireTimeLimit = 0.1f;
            range = 10;

            tankID = ID.CreateID();

            tankBoundingSphere.Radius = 1.443f;

            sensorBoundingSphere = new BoundingSphere();
            sensorBoundingSphere.Radius = 10;

            objectModel = new MModel("Media\\tank", position, direction);
            changeColor(team);

            mGLPos = position;
        }


        public Tank(Game game, Vector3 nPosition, Vector3 nDirection, TeamColor nTeam, TankState _ts)
            :base(game)
        {
            gameRef = game;

            mStateMachine = new StateMachine(this, _ts);

            mType = ObjectType.DYNAMIC;
            team = nTeam;           //set your current team
            
            healthPoint = 50;
            dead = false;

            position = nPosition;

            direction = nDirection;
            direction.Normalize();

            turnSpeed = (float)(60 * Math.PI) / 180;
            turnSpeed *= 2;
            moveSpeed = 10.0f;

            fireTime = 0.0f;
            fireTimeLimit = 0.5f;
            range = 10;

            tankID = ID.CreateID();

            tankBoundingSphere.Radius = 1.443f;

            sensorBoundingSphere = new BoundingSphere();
            sensorBoundingSphere.Radius = 10;

            objectModel = new MModel("Media\\tank", position, direction);
            changeColor(team);

            mGLPos = position;
        }

        #endregion Constructor

        #region Method

        #region Inherited

        protected override void Create()
        {
            IGraphicsDeviceService graphicsService = (IGraphicsDeviceService)mGame.Services.GetService(typeof(IGraphicsDeviceService));
        }

        public override void Initialize()
        {
        }

        public override void Update(GameTime gTime)
        {
            //update tank model
            objectModel.POSITION = position;
            objectModel.Update(gTime);

            //update bounding volumes
            Vector3 offset = new Vector3(0, 0, 0.33f);
            sensorBoundingSphere.Center = position + offset;
            tankBoundingSphere.Center = position + offset;

            CheckForCollisions();

            if (healthPoint <= 0)
            {
                dead = true;
            }

            changeColor(team);

            //update state machine
            mStateMachine.Update(gTime);
        }

        public override void Draw(GameTime gTime)
        {
            if (dead == false)
            {
                objectModel.Draw();
            }
        }

        #endregion

        #region Movement

        /*
         * The basic movement and turn interface shall become accessable to the State. Functions will now take
         * what is desired from them in measurements and return whether they were able to complete the task in the 
         * given turn with a boolean.
         */

        public bool Forward(GameTime gtime, float distance)
        {
            float datm;     //Distance Able To Move

            datm = (float)gtime.ElapsedGameTime.TotalSeconds * moveSpeed;

            if (distance > datm)
            {
                position = position + (direction * datm);
                return false;
            }
            else
            {
                position = position + (direction * distance);
                return true;
            }
        }

        public bool Reverse(GameTime gTime, float distance)
        {
            float datm;         //Distance Able To Travel
            Vector3 moveDir;    //movement direction, inverted tank's direction

            datm = (float)gTime.ElapsedGameTime.TotalSeconds * moveSpeed;
            moveDir = -1 * direction;

            if (distance > datm)
            {
                position = position + (moveDir * datm);
                return false;
            }
            else
            {
                position = position + (moveDir * distance);
                return true;
            }
        }

        public bool turnLeft(GameTime gtime, float turnArc)
        {
            float aatt;             //angle able to turn
            Matrix turnMatrix;      //turn transformation

            aatt = turnSpeed * (float)gtime.ElapsedGameTime.TotalSeconds;

            if (turnArc > aatt)
            {
                turnMatrix = Matrix.CreateRotationZ(aatt);
                direction = Vector3.Transform(direction, turnMatrix);
                objectModel.DIRECTION = direction;
                return false;
            }
            else
            {
                turnMatrix = Matrix.CreateRotationZ(turnArc);
                direction = Vector3.Transform(direction, turnMatrix);
                objectModel.DIRECTION = direction;
                return true;
            }
        }

        public bool turnRight(GameTime gtime, float turnArc)
        {
            float aatt;         //Angle Able To Turn
            Matrix turnMatrix;         //turn transformation

            aatt = turnSpeed * (float)gtime.ElapsedGameTime.TotalSeconds;

            if (turnArc > aatt)
            {
                turnMatrix = Matrix.CreateRotationZ(-aatt);
                direction = Vector3.Transform(direction, turnMatrix);
                objectModel.DIRECTION = direction;
                return false;
            }
            else
            {
                turnMatrix = Matrix.CreateRotationZ(-turnArc);
                direction = Vector3.Transform(direction, turnMatrix);
                objectModel.DIRECTION = direction;
                return true;
            }
        }

        #endregion

        #region Collision

        /*
         * called when the tank has a collision, impact or senses.
         * notify AI state of input
         */

        public void sense(Tank _t)
        {
            //notify AI state of sensor hit
            mStateMachine.CurrentState.sensorInput(this, (Object)_t);

            //changeColor(Color.Green);
        }

        public void hit(Projectile _p)
        {
            //notufy AI state of projectile impact
            mStateMachine.CurrentState.collisionInput(this, _p);

            healthPoint -= 10;

            changeColor(Color.Red);
        }

        public void collided(Tank _t)
        {
            //notify AI state of collision
            mStateMachine.CurrentState.collisionInput(this, _t);

            changeColor(Color.White);
        }

        public void collided(Plane _p)
        {
            mStateMachine.CurrentState.collisionInput(this, _p);

            changeColor(Color.Gray);
        }


        /*
         * check tank collision tank bounding against other collision volume
         */
        public override bool Collide(BoundingBox pBox)
        {
            return tankBoundingSphere.Intersects(pBox);
        }

        public override bool Collide(BoundingSphere pSphere)
        {
            return tankBoundingSphere.Intersects(pSphere);
        }

        public bool Collide(Plane plane)
        {
            PlaneIntersectionType pit;
            pit = tankBoundingSphere.Intersects(plane);

            if (pit == PlaneIntersectionType.Intersecting)
            {
                return true;
            }

            return false;
        }

        public void CheckForCollisions()
        {
            foreach (Object o in MUSA.mObjectList)
            {
                //process tanks only(hopefully, requires verification)
                if (o is Tank)
                {
                    Tank t = (Tank)o;

                    //exclude self from collision check
                    if (t.TankID == this.TankID) 
                    {
                        continue;
                    }

                    //check for collision(self, view field)
                    if (Collide(t.tankBoundingSphere))
                    {
                        collided(t);
                    }

                    else if( Collide(t.sensorBoundingSphere) )
                    {
                        sense(t);
                    }
                }
            }

            foreach (Plane p in MUSA.mBoundList)
            {
                if (Collide(p))
                {
                    collided(p);
                }
            }
        }

        public void CheckForPlaneCollisions()
        {
            //need to fill out
        }

        //check for object along given direction
            //is this still going to be used
        public CollisionInfo CheckForCollisions(Vector3 Direction)
        {
            Ray checkRay = new Ray(position, direction);
            foreach (Object item in (MUSA.mObjectList))
            {
                //if you are yourself, don't detect youself as a collidable object
                if (item == this)
                    continue;

                //Check whether the ray hits the object
                //if (checkRay.Intersects(item.BoundBox) < 50)
                //{
                //    if (item.Type == ObjectType.STATIC)
                //    {
                //        return new CollisionInfo(ObjectType.STATIC , item.Position , TeamColor.NONE);
                //    }
                //    else
                //    {
                //        Tank temp = (Tank)item;
                //        return new CollisionInfo(ObjectType.DYNAMIC , temp.Position , temp.Team);
                //    }
                //}
            }

            return null;
        }

        #endregion

        #region Other

        public void setState(GameTime gTime, State _state)
        {
            mStateMachine.ChangeState(gTime, _state);
        }

        public bool fire(GameTime gtime)
        {
            fireTime += (float)gtime.ElapsedGameTime.TotalSeconds;

            if (fireTime >= fireTimeLimit)
            {
                fireTime = 0;
                Projectile p = new Projectile(gameRef, position, direction, tankID);
                ProjectileManager.addProjectile(p);
                return true;
            }

            return false;
        }

        public bool WithinGroupRadius()
        {
            Vector3 tvec = position - mGLPos;
            if (tvec.Length() < 1f)
                return true;
            else
                return false;
        }

        public void changeColor(Color newColor)
        {
            objectModel.COLOR = newColor;
        }

        public void changeColor(TeamColor newColor)
        {
            switch (newColor)
            {
                case TeamColor.BLUE:
                    changeColor(Color.Blue);
                    break;

                case TeamColor.RED:
                    changeColor(Color.Red);
                    break;

                case TeamColor.GREEN:
                    changeColor(Color.Green);
                    break;

                case TeamColor.YELLOW:
                    changeColor(Color.Yellow);
                    break;

                default:
                    changeColor(Color.Black);
                    break;
            }
        }

        #endregion

        #endregion
    }
}